from string import digits
from StringIO import StringIO
from sys import exit
from optparse import OptionParser

DEFAULT_CODE = 'epro.code'

class CodeError(Exception):
  pass

class RunError(Exception):
  pass

# equals bool(re.match('[-+]?\d+\Z', s))
def chknum(s):
  if s.startswith(('+', '-')):
    s = s[1:]
  if s:
    for c in s:
      if c not in digits:
        break
    else:
      return True
  return False

class Vm:
  OOPS=["add", "sub", "mul", "div", "god", "jpo", "gto", "pop", "pri", "red", "hal"]
  TOPS=["tod", "tto"]

  def __init__(self, codestr, usetape=False):
    ops = self.OOPS + self.TOPS if usetape else self.OOPS
    self.code=[]
    sio=StringIO(codestr)
    for l in sio:
      l=l.strip()
      if chknum(l):
        self.code.append(int(l))
      elif l in ops:
        self.code.append(l)
      else:
        raise CodeError("unknown instruction: "+l)
    sio.close()

    self.stack=[]
    self.pc = self.step = self.max = 0
    if usetape: self.tape={}

  def run(self, sil=False):
    print('*** START ***')
    try:
      while True:
        if not 0<=self.pc<len(self.code):
          raise RunError('cannot fetch next instruction')
        i=self.code[self.pc]
        if isinstance(i, (int, long)):
          self.con(i)
        elif i=="add":
          self.add()
        elif i=="sub":
          self.sub()
        elif i=="mul":
          self.mul()
        elif i=="div":
          self.div()
        elif i=="god":
          self.god()
        elif i=="jpo":
          self.jpo()
        elif i=="gto":
          self.gto()
        elif i=="pop":
          self.pop()
        elif i=="tod":
          self.tod()
        elif i=="tto":
          self.tto()
        elif i=="pri":
          self.pri()
        elif i=="red":
          self.red(sil)
        elif i=='hal':
          try:
            self.ecode=self.stack.pop()
            break
          except IndexError:
            raise RunError('hal: stack is empty')
        self.step+=1
    except RunError, e:
      print("*** ERROR [%s] *** (%s steps, %s stacks)" % (e, self.step, self.max))
    else:
      if self.ecode==0:
        print("*** HALT *** (%s steps, %s stacks)" % (self.step, self.max))
      else:
        print("*** HALT [%s] *** (%s steps, %s stacks)" % (self.ecode, self.step, self.max))

  def con(self, n):
    if len(self.stack)==self.max:
      self.max+=1
    self.stack.append(n)
    self.pc+=1

  def pop(self):
    try:
      a=self.stack.pop()
    except IndexError:
      raise RunError('pop: stack is empty')
    self.pc+=1

  def add(self):
    try:
      y=self.stack.pop()
      x=self.stack.pop()
    except IndexError:
      raise RunError('add: stack is empty')
    self.stack.append(x+y)
    self.pc+=1

  def sub(self):
    try:
      y=self.stack.pop()
      x=self.stack.pop()
    except IndexError:
      raise RunError('sub: stack is empty')
    self.stack.append(x-y)
    self.pc+=1

  def mul(self):
    try:
      y=self.stack.pop()
      x=self.stack.pop()
    except IndexError:
      raise RunError('mul: stack is empty')
    self.stack.append(x*y)
    self.pc+=1

  def div(self):
    try:
      y=self.stack.pop()
      x=self.stack.pop()
    except IndexError:
      raise RunError('div: stack is empty')
    try:
      q=x//y
    except ZeroDivisionError:
      raise RunError('div: division by zero')
    if q>=0 or x%y==0:
      self.stack.append(q)
    else:
      self.stack.append(q+1)
    self.pc+=1

  def pri(self):
    try:
      a=self.stack.pop()
    except IndexError:
      raise RunError('pri: stack is empty')
    print(a)
    self.pc+=1

  def god(self):
    try:
      n=self.stack.pop()
    except IndexError:
      raise RunError('god: stack is empty')
    if not 0<=n<len(self.stack):
      raise RunError('god: stack out of bounds')
    self.stack.append(self.stack[n])
    self.pc+=1

  def gto(self):
    try:
      n=self.stack.pop()
      k=self.stack.pop()
    except IndexError:
      raise RunError('gto: stack is empty')
    if not 0<=n<len(self.stack):
      raise RunError('gto: stack out of bounds')
    self.stack[n]=k
    self.pc+=1

  def red(self, sil=False):
    if sil:
      try:
        t=raw_input()
      except EOFError:
        raise RunError('red: end of input')
      t=t.strip()
      if not chknum(t):
        raise RunError('red: input is not an integer!: '+t)
    else:
      while True:
        try:
          t=raw_input('input an integer?\n')
        except EOFError:
          raise RunError('red: end of input')
        t=t.strip()
        if chknum(t):
          print('OK')
          break
    if len(self.stack)==self.max:
      self.max+=1
    self.stack.append(int(t))
    self.pc+=1

  def jpo(self):
    try:
      n=self.stack.pop()
      k=self.stack.pop()
    except IndexError:
      raise RunError('jpo: stack is empty')
    if k>0:
      self.pc=n
    else:
      self.pc+=1

  def tod(self):
    try:
      n=self.stack.pop()
    except IndexError:
      raise RunError('tod: stack is empty')
    self.stack.append(self.tape.get(n, 0))
    self.pc+=1

  def tto(self):
    try:
      n=self.stack.pop()
      k=self.stack.pop()
    except IndexError:
      raise RunError('tto: stack is empty')
    self.tape[n]=k
    self.pc+=1

usage='usage: python %prog [options] [code]'
op=OptionParser(usage)
op.add_option('-s', default=False, action='store_true', dest='sil', help='silent mode')
op.add_option('-t', default=False, action='store_true', dest='tap', help='use tape')
opts, args = op.parse_args()

if len(args)==0:
  fname=None
elif len(args)==1:
  fname=args[0]
else:
  op.error('too many arguments')

try:
  if fname is None:
    f=open(DEFAULT_CODE, 'rU')
  else:
    try:
      f=open(fname, 'rU')
    except IOError:
      f=open(fname+'.code', 'rU')
except IOError:
  exit('cannot open ' + \
       (DEFAULT_CODE if fname is None else '%(fn)s or %(fn)s.code' % {'fn': fname}))
else:
  code=f.read()
  f.close()

try:
  Vm(code, opts.tap).run(opts.sil)
except CodeError, v:
  exit(v)
